package edu.neu.cs6515.farm;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.google.common.collect.ImmutableList;

import edu.neu.cs6515.admin.Administrator;
import edu.neu.cs6515.admin.RankingEntry;
import edu.neu.cs6515.player.AbstractStrategyPlayer;
import edu.neu.cs6515.player.Player;
import edu.neu.cs6515.player.strategy.MostPointsThenLowerColorsStrategy;
import edu.neu.cs6515.player.strategy.GreedyStrategy;
import edu.neu.cs6515.player.strategy.LowerColorsMorePointsStrategy;
import edu.neu.cs6515.player.strategy.LowestColorFirstStrategy;
import edu.neu.cs6515.player.strategy.MostSumPointsStrategy;
import edu.neu.cs6515.player.strategy.RandomStrategy;

/**
 * Allows users to run multiple complete games to evaluate the performance of
 * strategies
 */
public final class StrategyFarm {

    private static final int ROUNDS = 100;

    /**
     * Private C'tor for "util class"
     */
    private StrategyFarm() {
    }

    /**
     * Main function
     * 
     * @param args
     *            cmd line args
     */
    public static void main(final String[] args) {
        runAllDifferentStrategiesAgainstEachOther(false);
        mostPointsVSLowerColor(false);
        hybridVSMostPointsVSLowerColors(false);
        hybridVSLowerColors(false);
    }

    /**
     * Runs a game with 5 lowestScoreFirst players agains a single greedy player
     * 
     * @param traceSingleGameResults
     *            should the results of the single games be printed
     */
    @SuppressWarnings("unused")
    private static void runFiveLowestScoreFirstAgainstAGreedy(
            final boolean traceSingleGameResults) {
        printScenarioInfo("Runs a game with 5 lowestScoreFirst players agains a single greedy player");
        Player p1 =
                new Player("LowestScoreFirstPlayer1",
                        new LowestColorFirstStrategy());
        Player p2 =
                new Player("LowestScoreFirstPlayer2",
                        new LowestColorFirstStrategy());
        Player p3 =
                new Player("LowestScoreFirstPlayer3",
                        new LowestColorFirstStrategy());
        Player p4 =
                new Player("LowestScoreFirstPlayer4",
                        new LowestColorFirstStrategy());
        Player p5 =
                new Player("LowestScoreFirstPlayer5",
                        new LowestColorFirstStrategy());
        Player p6 =
                new Player("greedyPlayer6",
                        new GreedyStrategy());

        new StrategyFarm().runTestSet(
                ImmutableList.of(
                        p1,
                        p2,
                        p3,
                        p4,
                        p5,
                        p6),
                traceSingleGameResults);
    }

    /**
     * Nicely print infos about a scenario
     * 
     * @param info
     *            string about the scenario
     */
    private static void printScenarioInfo(
            final String info) {
        System.out.println("*************************************************************");
        System.out.println("Scenario: (Total number of games: "
                + ROUNDS
                + ")");
        System.out.println(info);
        System.out.println("*************************************************************");
    }

    /**
     * Runs different strategies against each other
     * 
     * @param traceSingleGameResults
     *            should the results of the single games be printed
     */
    private static void runAllDifferentStrategiesAgainstEachOther(
            final boolean traceSingleGameResults) {
        printScenarioInfo("Runs all different players against each other");

        Player p1 =
                new Player("MostPointsThenLowerColorsStrategy",
                        new MostPointsThenLowerColorsStrategy());
        Player p2 =
                new Player("LowestScoreFirstPlayer2",
                        new LowestColorFirstStrategy());
        Player p3 =
                new Player("greedyPlayer3",
                        new GreedyStrategy());
        Player p4 =
                new Player("MostSumPointsStrategy",
                        new MostSumPointsStrategy());
        Player p5 =
                new Player("LowerColorsMorePointsStrategy",
                        new LowerColorsMorePointsStrategy());

        new StrategyFarm().runTestSet(
                ImmutableList.of(p1, p2, p3, p4, p5),
                traceSingleGameResults);
    }

    /**
     * test 2 MostSumPointsStrategy agains 2 LowerColorsMorePointsStrategy
     * 
     * @param traceSingleGameResults
     */
    private static void mostPointsVSLowerColor(
            final boolean traceSingleGameResults) {
        printScenarioInfo("mostPointsVSLowerColor");

        Player p1 =
                new Player("MostSumPointsStrategy1",
                        new MostSumPointsStrategy());
        Player p2 =
                new Player("LowerColorsMorePointsStrategy1",
                        new LowerColorsMorePointsStrategy());
        Player p3 =
                new Player("MostSumPointsStrategy2",
                        new MostSumPointsStrategy());
        Player p4 =
                new Player("LowerColorsMorePointsStrategy2",
                        new LowerColorsMorePointsStrategy());

        new StrategyFarm().runTestSet(
                ImmutableList.of(p1, p2, p3, p4),
                traceSingleGameResults);
    }

    /**
     * run hybrid of MostSum and LowerColors against LowerColors
     * 
     * @param traceSingleGameResults
     */
    private static void hybridVSLowerColors(final boolean traceSingleGameResults) {
        printScenarioInfo("hybridVSMostPointsVSLowerColors");

        Player p1 =
                new Player("MostPointsThenLowerColorsStrategy",
                        new MostPointsThenLowerColorsStrategy());
        Player p2 =
                new Player("LowerColorsMorePointsStrategy1",
                        new LowerColorsMorePointsStrategy());

        new StrategyFarm().runTestSet(
                ImmutableList.of(p1, p2),
                traceSingleGameResults);
    }

    /**
     * run hybrid of MostSum and LowerColors against the original MostSum and
     * LowerColors
     * 
     * @param traceSingleGameResults
     */
    private static void hybridVSMostPointsVSLowerColors(final boolean traceSingleGameResults) {
        printScenarioInfo("hybridVSMostPointsVSLowerColors");

        Player p1 =
                new Player("MostSumPointsStrategy1",
                        new MostSumPointsStrategy());
        Player p2 =
                new Player("LowerColorsMorePointsStrategy1",
                        new LowerColorsMorePointsStrategy());
        Player p3 =
                new Player("MostPointsThenLowerColorsStrategy1",
                        new MostPointsThenLowerColorsStrategy());
        Player p4 =
                new Player("MostSumPointsStrategy2",
                        new MostSumPointsStrategy());
        Player p5 =
                new Player("LowerColorsMorePointsStrategy2",
                        new LowerColorsMorePointsStrategy());
        Player p6 =
                new Player("MostPointsThenLowerColorsStrategy2",
                        new MostPointsThenLowerColorsStrategy());

        new StrategyFarm().runTestSet(
                ImmutableList.of(p2, p3, p4, p5, p6, p1),
                traceSingleGameResults);
    }

    /**
     * Runs 3 random strategy players against each other
     * 
     * @param traceSingleGameResults
     *            should the results of the single games be printed
     */
    @SuppressWarnings("unused")
    private static void runThreeRandomStrategiesAgainstEachOther(
            final boolean traceSingleGameResults) {
        printScenarioInfo("Runs 3 random strategy players against each other");
        Player p1 =
                new Player("randomPlayer1",
                        new RandomStrategy());
        Player p2 =
                new Player("randomPlayer2",
                        new RandomStrategy());
        Player p3 =
                new Player("randomPlayer3",
                        new RandomStrategy());

        new StrategyFarm().runTestSet(
                ImmutableList.of(p1, p2, p3),
                traceSingleGameResults);
    }

    /**
     * Runs ROUNDS number complete test games
     * 
     * @param gamePlayers
     *            list of participants
     * @param traceSingleGameResults
     *            should the results of the single games be printed
     */
    private void runTestSet(
            final ImmutableList<Player> gamePlayers,
            final boolean traceSingleGameResults) {
        Map<String, Integer> testSetResults =
                new HashMap<String, Integer>();
        for (AbstractStrategyPlayer player : gamePlayers) {
            testSetResults.put(
                    player.getPlayersName(),
                    0);
        }
        for (int i = 0; i < ROUNDS; i++) {
            runSingleGame(
                    gamePlayers,
                    traceSingleGameResults,
                    testSetResults);
            System.out.print(".");
        }
        printFarmResults(testSetResults);
    }

    /**
     * @param gamePlayers
     *            list of participants
     * @param traceSingleGameResults
     *            should the results of a single game be printed
     * @param testSetResults
     *            map containing the number of wins for an individual player
     */
    private void runSingleGame(
            final ImmutableList<Player> gamePlayers,
            final boolean traceSingleGameResults,
            final Map<String, Integer> testSetResults) {
        Administrator gameAdmin =
                new Administrator(gamePlayers.size());
        for (Player player : gamePlayers) {
            gameAdmin.registerPlayer(
                    player,
                    player.getPlayersName());
        }
        List<RankingEntry> gameResults =
                gameAdmin.runGame();
        printGameDetails(
                traceSingleGameResults,
                gameResults);
        String winnersName =
                gameResults.get(0)
                        .getPlayersName();
        Integer newScore =
                testSetResults.get(winnersName) + 1;
        testSetResults.put(winnersName, newScore);
    }

    /**
     * If traceSingleGameResults is true, the rankings of an idividual game are
     * printed
     * 
     * @param traceSingleGameResults
     *            true if printing is required
     * @param gameResults
     *            results of the current game
     */
    private void printGameDetails(
            final boolean traceSingleGameResults,
            final List<RankingEntry> gameResults) {
        if (traceSingleGameResults) {
            for (RankingEntry rankingEntry : gameResults) {
                System.out.println(rankingEntry);
            }
            System.out.println("---------");
        }
    }

    /**
     * Prints the results of a complete test run, lasting ROUNDS
     * 
     * @param testSetResults
     *            results of the test runs
     */
    private void printFarmResults(
            final Map<String, Integer> testSetResults) {
        List<Entry<String, Integer>> list =
                new LinkedList<Entry<String, Integer>>(testSetResults.entrySet());
        Collections.sort(
                list,
                new Comparator<Entry<String, Integer>>() {
                    @Override
                    public int compare(
                            final Entry<String, Integer> o1,
                            final Entry<String, Integer> o2) {
                        return o1.getValue()
                                .compareTo(
                                        o2.getValue());
                    }
                });
        Collections.reverse(list);
        printFormatedTable(list);
    }

    /**
     * Prints a table with the final rankings
     * 
     * @param ranking
     *            ranking table to be printed
     */
    private void printFormatedTable(
            final List<Entry<String, Integer>> ranking) {
        System.out.println();
        System.out.println("-----------------------------------------------------");
        System.out.println("Rank\tName");
        System.out.println("-----------------------------------------------------");
        int rank = 1;
        for (Entry<String, Integer> rankingResult : ranking) {
            System.out.println(rank++
                    + ".\t"
                    + rankingResult.getKey()
                    + " (Wins: "
                    + rankingResult.getValue()
                    + ")");
        }
        System.out.println();
    }
}
